﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace V2
{
    /// <summary> Partitions (similar to "GROUP BY") a set by unique combinations of the 
    /// input <see cref="IStreamingSet"/> and <see cref="ColumnSet"/>'s results. Partitions are 
    /// available through <see cref="PartitionBinding"/>.
    /// </summary>
    public class SetPartition
    {
        private readonly IStreamingSet m_Input;
        private readonly ColumnSet m_Definition;
        private readonly Dictionary<MaskedValues, SubSet> m_Output;

        public SetPartition( IStreamingSet input, ColumnSet definition ) {
            m_Input = input;
            m_Definition = definition;
            m_Output = new Dictionary<MaskedValues, SubSet>();

            m_Input.RowBinding += HandleInput;
        }
        
        /// <summary>
        /// Returns/Creates subset associated with partition_values. If created then it will fire notification.
        /// </summary>
        /// <param name="partition_values"></param>
        /// <returns></returns>
        public IStreamingSet Request(MaskedValues partition_values)
        {
            SubSet associated_set;

            if (m_Output.TryGetValue(partition_values, out associated_set) == false)
            {
                associated_set = new SubSet(partition_values);
                m_Output.Add(partition_values, associated_set);

                FireSetCreated(partition_values, associated_set);
            }
            return associated_set;
        }

        private void HandleInput( IStreamingSet sender, ChangeArgs args ) {
            switch( args.ChangeType ) {
                case ChangeType.Add: HandleAddition( sender, args ); break;
                case ChangeType.Remove: HandleRemoval(sender, args); break;
                case ChangeType.Update: HandleUpdate(sender, args); break;
                case ChangeType.Clear: HandleClear(sender, args); break;
                default: throw new ArgumentOutOfRangeException();
            }
        }

        private void HandleClear( IStreamingSet sender, ChangeArgs args ) { throw new NotImplementedException(); }

        private void HandleAddition(IStreamingSet sender, ChangeArgs args) {
            foreach( Values addition in args.AdditionItems ) {
                HandleItemAddition( addition );
            }
        }

        private void HandleItemAddition( Values addition ) {
            MaskedValues partition_values = m_Definition.GetMaskedValues(addition);
            SubSet associated_set;
            if( m_Output.TryGetValue( partition_values, out associated_set ) == false ) {
                associated_set = new SubSet( partition_values );
                m_Output.Add( partition_values, associated_set );
                
                FireSetCreated( partition_values, associated_set );
            }
            // assume change notice should come through this.
            associated_set.Add( addition );
        }
        
        
        private void HandleRemoval( IStreamingSet sender, ChangeArgs args ) { throw new NotImplementedException(); }

        private void HandleItemRemoval( Values removal) { throw new NotImplementedException(); }

        private void HandleUpdate(IStreamingSet sender, ChangeArgs args)
        {
            foreach (ChangeItem item in args.Items)
            {
                Values current = m_Definition.GetSubset(item.Current);
                Values previous = m_Definition.GetSubset(item.Previous);
                
                if (current != previous)
                {
                    HandleItemRemoval(previous);
                    HandleItemAddition(current);
                }else {
                    throw new NotImplementedException("Should just bubble change notice, I think?");
                }
            }
        }

        private void FireSetCreated( MaskedValues associated_values, SubSet associated_set)
        {
            if (m_CorePartitionBinding != null) { m_CorePartitionBinding(associated_values, associated_set); }
        }

        /// <summary> A Set that's associated with a given Values. IndexBucket </summary>
        class SubSet : Set {
            private MaskedValues m_PartitionValues;
            public SubSet(MaskedValues partition_values) { m_PartitionValues = partition_values; }
        }

        private event PartitionHandler m_CorePartitionBinding;
        // it would be neat to use a Set here.
        public event PartitionHandler PartitionBinding {
            add {
                foreach( KeyValuePair<MaskedValues, SubSet> pair in m_Output ) {
                    value( pair.Key, pair.Value );
                }
                m_CorePartitionBinding += value;
            }
            remove { m_CorePartitionBinding -= value; }
        }
        
    }

    public delegate void PartitionHandler( MaskedValues partition_values, IStreamingSet partition);
}
